#pragma once
#include <cassert>
#include <chrono>
#include <cuda_runtime.h>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <random>
#include <regex>
#include <utility>
#include <vector>

#include <cuda_runtime.h>
#include <mma.h>
using namespace std;
#define CHECK_CUDA_ERROR(val) check((val), #val, __FILE__, __LINE__)
template <typename T>
void check(T err, const char *const func, const char *const file,
           int const line) {
    if (err != cudaSuccess) {
        std::cerr << "CUDA Runtime Error at: " << file << ":" << line
                  << std::endl;
        std::cerr << cudaGetErrorString(err) << " " << func << std::endl;
        std::exit(EXIT_FAILURE);
    }
}

#define CHECK_LAST_CUDA_ERROR() checkLast(__FILE__, __LINE__)
void checkLast(const char *const file, int const line) {
    cudaError_t const err{cudaGetLastError()};
    if (err != cudaSuccess) {
        std::cerr << "CUDA Runtime Error at: " << file << ":" << line
                  << std::endl;
        std::cerr << cudaGetErrorString(err) << std::endl;
        std::exit(EXIT_FAILURE);
    }
}

template <class T>
float measure_performance(std::function<T(cudaStream_t)> bound_function,
                          cudaStream_t stream, int num_repeats = 100,
                          int num_warmups = 100) {
    cudaEvent_t start, stop;
    float time;

    CHECK_CUDA_ERROR(cudaEventCreate(&start));
    CHECK_CUDA_ERROR(cudaEventCreate(&stop));

    for (int i{0}; i < num_warmups; ++i) {
        bound_function(stream);
    }

    CHECK_CUDA_ERROR(cudaStreamSynchronize(stream));

    CHECK_CUDA_ERROR(cudaEventRecord(start, stream));
    for (int i{0}; i < num_repeats; ++i) {
        bound_function(stream);
    }
    CHECK_CUDA_ERROR(cudaEventRecord(stop, stream));
    CHECK_CUDA_ERROR(cudaEventSynchronize(stop));
    CHECK_LAST_CUDA_ERROR();
    CHECK_CUDA_ERROR(cudaEventElapsedTime(&time, start, stop));
    CHECK_CUDA_ERROR(cudaEventDestroy(start));
    CHECK_CUDA_ERROR(cudaEventDestroy(stop));

    float const latency{time / num_repeats};

    return latency;
}

void fill_random_float_values(float *arr, size_t n,
                              std::default_random_engine &e) {
    std::uniform_real_distribution<float> uniform_dist(-256, 256);
    for (size_t i{0}; i < n; ++i) {
        arr[i] = uniform_dist(e);
    }
}

void fill_random_int8_values(int8_t *arr, size_t n,
                             std::default_random_engine &e) {
    std::uniform_int_distribution<int8_t> uniform_dist(-128, 127);
    for (size_t i{0}; i < n; ++i) {
        arr[i] = uniform_dist(e);
    }
}

void fill_random_int32_values(int32_t *arr, size_t n,
                              std::default_random_engine &e) {
    std::uniform_int_distribution<int32_t> uniform_dist(-128, 127);
    for (size_t i{0}; i < n; ++i) {
        arr[i] = uniform_dist(e);
    }
}

void float2half(__half *half_arr, float const *float_arr, size_t n) {
    for (size_t i{0}; i < n; ++i) {
        half_arr[i] = __float2half(float_arr[i]);
    }
}

template <typename T>
float get_avg_abs_diff_ratio(T const *arr_1, T const *arr_2, size_t n) {
    float sum_abs_diff_ratio{0};
    for (size_t i{0}; i < n; ++i) {
        sum_abs_diff_ratio += std::abs(static_cast<float>(arr_1[i]) -
                                       static_cast<float>(arr_2[i]));
    }
    return sum_abs_diff_ratio / n;
}

template <typename T>
bool array_equal(T const *arr_1, T const *arr_2, size_t n) {
    for (size_t i{0}; i < n; ++i) {
        if (arr_1[i] != arr_2[i]) {
            return false;
        }
    }
    return true;
}

void print_test_header(bool is_A_transpose, bool is_B_transpose) {
    // C = A * B
    if ((!is_A_transpose) && (!is_B_transpose)) {
        std::cout << "C = A * B" << std::endl;
    }
    // C = A^T * B
    else if ((is_A_transpose) && (!is_B_transpose)) {
        std::cout << "C = A^T * B" << std::endl;
    }
    // C = A * B^T
    else if ((!is_A_transpose) && (is_B_transpose)) {
        std::cout << "C = A * B^T" << std::endl;
    }
    // C = A^T * B^T
    else {
        std::cout << "C = A^T * B^T" << std::endl;
    }
}

template <typename T> void init_row(T *data, const int m, const int n) {
    for (int r = 0; r < m; r++) {
        int v = 0;
        for (int c = 0; c < n; c++) {
            data[r * n + c] = static_cast<T>(v++);
        }
    }
}

template <typename T> void init_col(T *data, const int m, const int n) {
    for (int c = 0; c < n; c++) {
        int v = 0;
        for (int r = 0; r < m; r++) {
            data[r * n + c] = static_cast<T>(v++);
        }
    }
}

template <typename T>
void randu(T *data, const int N, const int min, const int max,
           const unsigned seed = 123) {
    std::random_device rd;
    std::mt19937 gen(seed);
    std::uniform_int_distribution<> dis(min, max);

    for (int i = 0; i < N; i++) {
        data[i] = static_cast<T>(dis(gen));
    }
}

template <typename T> std::vector<T> extractNumbers(const std::string &text) {
    std::regex pattern("-?\\d+");
    std::smatch matches;
    std::vector<T> numbers;
    auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
    auto end = std::sregex_iterator();

    for (std::sregex_iterator i = begin; i != end; ++i) {
        if (i == begin) {
            continue;
        }
        numbers.push_back(static_cast<T>(std::stoi((*i).str())));
    }
    return numbers;
}
vector<string> file_to_lines(string &filename) {
    std::ifstream file(filename);
    vector<string> result;

    // 检查文件是否成功打开
    if (!file) {
        std::cerr << "无法打开文件 " << filename << std::endl;
    }
    // 读取文件中的每一行
    std::string line;
    while (std::getline(file, line)) {
        result.push_back(line);
    }
    // 关闭文件
    file.close();
    return result;
}

template <typename T> void str_to_num(T *data, string name, const int total) {
    int i = 0;
    auto line_to_str = extractNumbers<T>(name);
    for (auto e : line_to_str) {
        data[i++] = e;
        if (i == total) {
            break;
        }
    }
}
template <typename T> bool check(T *a, T *b, const int n) {
    bool flag = true;
    for (int i = 0; i < n; i++) {
        if (abs(a[i] - b[i]) > 1e-4) {
            flag = false;
            std::cout << "Matrix A != B first LHS(CPU) " << a[i] << " RHS(GPU) "
                      << b[i] << "\n";
            break;
        }
    }
    return flag;
}
template <typename T>
void matmul(T *C, const T *A, const T *B, const int m, const int n,
            const int k) {
    for (int i_m = 0; i_m < m; i_m++) {
        for (int i_n = 0; i_n < n; i_n++) {
            T acc{0};
            for (int i_k = 0; i_k < k; i_k++) {
                acc += A[i_m * k + i_k] * B[i_k * n + i_n];
            }
            C[i_m * n + i_n] = acc;
        }
    }
}
void vector_to_file(const string &filename, float *data, const int N) {
    std::ofstream file(filename);
    if (file.is_open() && file.good()) {
        for (int i = 0; i < N; i++) {
            if (i != N - 1) {
                file << data[i] << " ";
            } else {
                file << data[i];
            }
        }
    }
}
